package cn.jasonone.at;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;
import javax.sql.DataSource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import cn.jasonone.at.annotation.Column;
import cn.jasonone.at.annotation.Table;
import cn.jasonone.at.enums.ATType;
import cn.jasonone.at.exception.AutoTableException;
import cn.jasonone.at.model.ATField;
import cn.jasonone.at.model.ColumnInfo;
import cn.jasonone.at.model.PrimaryKey;
import cn.jasonone.at.model.TableInfo;
import cn.jasonone.at.template.syntax.Syntax;
import cn.jasonone.at.type.AutoTableType;
import cn.jasonone.at.util.AnnotationUtil;
import cn.jasonone.at.util.ColumnFactory;
import cn.jasonone.at.util.DataBaseUtil;
import cn.jasonone.at.util.SqlUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * AutoTable 业务启动类
 * 
 * @author Jason
 *
 */
@Component
@Slf4j
public class AutoTableApplicationRunner implements ApplicationRunner {
	@Resource
	private ApplicationContext applicationContext;
	@Resource
	private AutoTableProperties properties;
	@Resource
	private DataSource dataSource;

	/**
	 * 获得当前项目的根包
	 * 
	 * @return
	 */
	private String getRootPackage() {
		Map<String, Object> map = applicationContext.getBeansWithAnnotation(SpringBootApplication.class);
		if (!map.isEmpty()) {
			Collection<Object> values = map.values();
			for (Object obj : values) {
				Class<? extends Object> applicationType = obj.getClass();
				return applicationType.getPackage().getName();
			}
		}
		return "";
	}

	/**
	 * 扫描实体类
	 * 
	 * @return
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	private List<Class<?>> scannerByAnnotation() throws ClassNotFoundException, IOException {
		Set<String> names = new HashSet<>();
		List<Class<?>> classlist = new ArrayList<>();
		List<String> baseBeanPackages = properties.getBaseBeanPackages();
		// 如果指定了扫描的基础包,则只扫描基础包
		if (baseBeanPackages != null && !baseBeanPackages.isEmpty()) {
			for (String packageName : baseBeanPackages) {
				List<Class<?>> list = AnnotationUtil.scannerByAnnotation(packageName, Table.class);
				list.stream().filter(c -> names.add(c.getName())).forEach(c -> classlist.add(c));
			}
		} else {
			// 如果未指定基础包,则扫描当前项目下的实体
			List<Class<?>> list = AnnotationUtil.scannerByAnnotation(getRootPackage(), Table.class);
			list.stream().filter(c -> names.add(c.getName())).forEach(c -> names.add(c.getName()));
		}
		return classlist;
	}

	@Override
	public void run(ApplicationArguments args) throws Exception {
		log.info("AutoTable 开始工作");
		log.info("当前启动模式:{}", properties.getType());
		if (properties.getType() == ATType.NONE) {
			log.warn("由于启动模式为[{}],AutoTable不做任何处理", ATType.NONE);
			log.info("AutoTable 停止工作");
			return;
		}
		if (StringUtils.isBlank(properties.getCatalog())) {
			throw new AutoTableException("为找到配置:${auto-table.catalog}");
		}
		log.info("初始化:ColumnFactory");
		ColumnFactory.setProperties(properties);
		Map<String, Syntax> syntaxs = applicationContext.getBeansOfType(Syntax.class);
		log.info("初始化:SqlUtil");
		SqlUtil.init(syntaxs.values());
		try (Connection connection = dataSource.getConnection()) {
			DatabaseMetaData md = connection.getMetaData();
			log.info("初始化:DataBaseUtil");
			DataBaseUtil.setDatabaseMetaData(md);
			// 获取数据库类型
			String type = md.getDatabaseProductName().toLowerCase();
			// 准备Sql代码存放容器
			List<String> sqlList = new ArrayList<>();
			// 获取需要创建表的实体对象
			List<Class<?>> tableClasses = scannerByAnnotation();
			// 获取Sql配置
			Map<String, DataBaseSql> sqlMap = properties.getSql();
			if (!sqlMap.containsKey(type)) {
				log.info("支持的数据库类型:{}", sqlMap.keySet());
				throw new AutoTableException("数据库类型错误或暂不支持数据库:" + type);
			}
			// 得到对应数据库的Sql配置对象
			DataBaseSql dbSql = sqlMap.get(type);
			// 扫描实体
			List<TableInfo> tables = scannerEntiry(type, tableClasses);
			Map<ATType, Class<? extends AutoTableType>> handlers = properties.getHandlers();
			if (!handlers.containsKey(properties.getType())) {
				throw new AutoTableException("[{}]模式的处理未找到,请检查[${auto-table.handlers}]配置");
			}
			Class<? extends AutoTableType> handleType = handlers.get(properties.getType());
			AutoTableType handle = applicationContext.getBean(handleType);
			handle.setAutoTableProperties(properties);
			log.info("开始构建DDL...");
			handle.autoTable(dbSql, sqlList, tables);
			log.info("开始执行DDL...");
			// 集中运行Sql
			try (Statement statement = connection.createStatement()) {
				for (String sql : sqlList) {
					if (StringUtils.isNoneBlank(sql)) {
						log.debug("执行Sql:{}", sql);
						statement.addBatch(sql);
					}
				}
				statement.executeBatch();
			}
			log.info("AutoTable处理完成!");
		} catch (ClassNotFoundException | IOException | SQLException e) {
			e.printStackTrace();
		}
	}

	private List<TableInfo> scannerEntiry(String type, List<Class<?>> tableClasses) {
		List<TableInfo> tables = new ArrayList<>(10);
		log.info("正在扫描实体...");
		TableInfo table = null;
		List<ColumnInfo> columns = null;
		List<PrimaryKey> primaryKeys = null;
		for (Class<?> clazz : tableClasses) {
			columns = new ArrayList<>(15);
			primaryKeys = new ArrayList<>(5);
			// 解析表信息
			table = new TableInfo();
			table.setColumns(columns);
			table.setPrimaryKeys(primaryKeys);

			table.setCatalog(properties.getCatalog());
			Table tableAnnotation = clazz.getDeclaredAnnotation(Table.class);
			table.setTableName(AnnotationUtil.getValue(tableAnnotation, "name", clazz.getSimpleName()));
			table.setComment(tableAnnotation.comment());
			table.setEngine(tableAnnotation.engine());

			// 设置临时字段
			ColumnInfo tempColumn = new ColumnInfo();
			tempColumn.setColumnName(properties.getTempColumnName());
			tempColumn.setTableName(table.getTableName());
			tempColumn.setCatalog(table.getCatalog());
			table.setTempColumn(tempColumn);
			// 开始解析字段
			List<ATField> fields = AnnotationUtil.fieldsByAnnotation(clazz, Column.class);
			ColumnInfo column = null;
			PrimaryKey primaryKey = null;
			for (ATField field : fields) {
				// 解析字段
				column = ColumnFactory.getColumnInfo(type, field);
				column.setTableName(table.getTableName());
				columns.add(column);
				// 检测是否为主键
				if (column.isPrimaryKey()) {
					primaryKey = new PrimaryKey();
					primaryKey.setCatalog(column.getCatalog());
					primaryKey.setTableName(column.getTableName());
					primaryKey.setColumnName(column.getColumnName());
					primaryKey.setAutoincrement(column.getAutoincrement() != null && column.getAutoincrement() == true);
					primaryKeys.add(primaryKey);
				}
			}
			log.info("类名:{}  表名:{} 字段数:{} 主键数:{}", clazz.getName(), table.getTableName(), columns.size(),
					primaryKeys.size());
			tables.add(table);
		}
		return tables;
	}

}
